﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Amazon.EC2.Model;
using Amazon.ElasticLoadBalancing.Model;
using Web_App___AWS_ELB_Monitor.Models;
using Web_App___AWS_ELB_Monitor.Models.ELB;
using Web_App___AWS_ELB_Monitor.Entities;

namespace Web_App___AWS_ELB_Monitor.Controllers
{
    public class ELBMonitorController : Controller
    {
        //cache for list of load-balancers to work with
        IEnumerable<LoadBalancerDescription> _loadBalancers = null;

        //seeded random for referring to
        static readonly Random R = new Random();

        //cache for list of configured load-balancer names we are told to ignore
        string[] _badLBNames;

        /// <summary>
        /// Retreives the list of load-balancer names we are told to ignore (this value is not retreived more than once)
        /// </summary>
        private IEnumerable<string> BadLBNames
        {
            get
            {
                //don't look up the value from config more than we have to
                if (_badLBNames == null)
                {
                    //get value from config
                    var configValue = ConfigurationManager.AppSettings["ELB.IgnoreNames"];
                    //test value
                    if (!String.IsNullOrWhiteSpace(configValue))
                    {
                        //if we have a value, split on comma
                        _badLBNames = configValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    }
                    else
                    {
                        //set as empty
                        _badLBNames = new string[] { };
                    }


                }
                //return cached value
                return _badLBNames;
            }
        }


        public ELBMonitorController()
        {
            //don't look up the value from web-service more than we have to
            if (_loadBalancers == null)
            {
                //set load-balancer list
                _loadBalancers =
                    ELBDataHelper.GetAllLoadBalancers() //all load balancers from web-service
                    .Where(lb => !BadLBNames.Contains<string>(lb.LoadBalancerName) //which we aren't configured to ignore
                    );
            }
        }


        private ELBMonitorDataViewModel GetRandomELBMonitorViewModel(bool getRequestData)
        {
            var skipAmount = R.Next(0, _loadBalancers.Count());

            var lb = _loadBalancers.Skip(skipAmount).First();

            var result = GetSingleELBMonitorVM(lb, getRequestData);

            return result;
        }

        private ELBMonitorDataViewModel GetSingleELBMonitorVM(LoadBalancerDescription lb, bool getRequestData)
        {
            //read instance list from elb and instance details from ec2
            var instanceResult = EC2DataHelper.GetInstancesForLoadBalancer(lb);
            var instances = instanceResult.InstanceDescriptions.ReadInstancesFromLoadBalancer();

            //get cloud-watch data and return in VM
            return new ELBMonitorDataViewModel
                       {
                           LoadBalancerName = lb.LoadBalancerName,
                           LoadBalancerUrl = ELBDataHelper.GetUrlForELB(lb.LoadBalancerName) ?? "http://" + lb.DNSName,
                           RequestChartDataTY1W2W = CloudWatchDataHelper.GetLoadBalancerDataMultipleTimeSpans(
                                                                lb.LoadBalancerName,
                                                                 DateTimeOffset.UtcNow.UtcDateTime.Date,
                                                                                    DateTimeOffset.UtcNow.UtcDateTime.Date.AddDays(-01),
                                                                                    DateTimeOffset.UtcNow.UtcDateTime.Date.AddDays(-07),
                                                                                    DateTimeOffset.UtcNow.UtcDateTime.Date.AddDays(-14)
                                                                                        ).ToDatePointGraph().OrderBy(graph => graph.TimeStamp),
                           InstanceCPUDataCurrent = instances.OrderBy(EC2DataHelper.GetInstanceFriendlyName).ToDictionary(
                               instance => instance.InstanceId,
                               instance => new List<DataPoint>{
                                   CloudWatchDataHelper.GetEC2InstanceSingleMetric(instance.InstanceId,
                                   DateTimeOffset.UtcNow.AddMinutes(-1),DateTimeOffset.UtcNow,
                                   AnalyticsValueType.Average,CloudWatchDefaultMetricEC2.CPUUtilization,"Percent")}

                               ),
                           BadInstanceIds = instanceResult.BadInstanceIds.ToArray()
                       };
        }

        public ActionResult Index()
        {
            return RedirectToAction("Random");
        }

        public ActionResult Random()
        {
            //get first site, send data to 
            var vm = new ELBMonitorViewModel
            {
                Data = GetRandomELBMonitorViewModel(true),
                Randomise = true
            };

            return View("ELBGraph", vm);
        }

        public ActionResult LoadBalancer(string loadBalancerName)
        {
            if (_loadBalancers == null)
                throw new Exception("No Load Balancers on this account.");
            if (String.IsNullOrWhiteSpace(loadBalancerName))
                throw new Exception("A value for loadBalancerName was not specified.");

            //get requested LB data
            var lb =
                _loadBalancers.Where(lbi => lbi.LoadBalancerName.ToLower().StartsWith(loadBalancerName.ToLower())).FirstOrDefault();

            //handle error situation
            if (lb == null)
                throw new Exception("LoadBalancer not beginning with name: " + loadBalancerName);

            //retreive metrics for the LB as a VM
            var vm = new ELBMonitorViewModel
            {
                Data = GetSingleELBMonitorVM(lb, true),
                Randomise = false
            };

            //combine view and model
            return View("ELBGraph", vm);
        }



        public ActionResult HealthPreview(string loadBalancerName, string previewUrl)
        {
            //get data
            var lb =
                _loadBalancers.Where(lbi => lbi.LoadBalancerName.ToLower().StartsWith(loadBalancerName.ToLower())).FirstOrDefault();

            var instancesResult = EC2DataHelper.GetInstancesForLoadBalancer(lb);
            var instances = instancesResult.InstanceDescriptions.ReadInstancesFromLoadBalancer();

            var model = new ELBPreviewViewModel()
                            {
                                ServerUrls = instances.Select(
                                    ins =>
                                    new ELBPreviewSiteSingleServer
                                        {
                                            ServerId = ins.InstanceId,
                                            ServerName = EC2DataHelper.GetInstanceFriendlyName(ins),
                                            HostName = ins.PublicDnsName,
                                            IsHealthy = true,
                                            SiteUrl = ELBDataHelper.GetELBPreviewUrl(ins.PublicDnsName, lb.HealthCheck.Target, previewUrl),
                                            Port = ELBDataHelper.GetELBPreviewPort(lb.HealthCheck.Target),
                                            Zone = ins.Placement.AvailabilityZone
                                        }).OrderBy(x => x.Zone),
                                ELBHealthCheckTarget = lb.HealthCheck.Target,
                                ELBName = lb.LoadBalancerName,
                                BadInstanceIds = instancesResult.BadInstanceIds.ToArray()
                            };

            return View("ELBPreview", model);
        }



        public ActionResult LoadBalancerData(string loadBalancerName)
        {



            var model =
                (String.IsNullOrWhiteSpace(loadBalancerName))
                    ? GetRandomELBMonitorViewModel(false)
                    : GetSingleELBMonitorVM(
                        _loadBalancers.Where(lbi => lbi.LoadBalancerName.ToLower().StartsWith(loadBalancerName.ToLower())).
                            FirstOrDefault(), false);

            return Json(
                new
                {
                    LoadBalancerName = model.LoadBalancerName,
                    LoadBalancerUrl = model.LoadBalancerUrl,
                    Data = model.RequestChartDataTY1W2W
                                        .Select(graph =>
                                            new
                                            {
                                                Date = graph.TimeStamp.ToString("HH:mm"),
                                                Data = graph.Value
                                            }
                                            ),
                    CPU = model.InstanceCPUDataCurrent.Select(item =>
                        new
                        {
                            Instance = item.Key,
                            Data = item.Value.Select(data => (data != null ? Math.Round(data.Value, MidpointRounding.AwayFromZero) : 0.0))
                        }
                        )

                }
                , JsonRequestBehavior.AllowGet);
        }

        public ActionResult InstanceHealthStatus(string loadBalancerName)
        {
            var utcNow = DateTimeOffset.UtcNow;

            //  -  Which instances are InService / OutOfService
            var instancesWithHealth = ELBDataHelper.GetInstanceHealthStatuses(loadBalancerName);
            
            var instancesResult = EC2DataHelper.GetInstances(instancesWithHealth.Select(ins => ins.InstanceId).ToArray());
            var badInstanceIds = instancesResult.BadInstanceIds;

            var instanceDetails = instancesResult.InstanceDescriptions.ReadInstancesFromLoadBalancer();
            var result = instanceDetails
                .Select(currentInstance =>
                            {
                                var instanceHealthRaw = instancesWithHealth.FirstOrDefault(ins => ins.InstanceId == currentInstance.InstanceId);
                                var instanceFriendlyName = EC2DataHelper.GetInstanceFriendlyName(currentInstance);
                                // The AWS API sometimes returns no data for the last 60 seconds, so get the last 120 seconds, order by timestamp descending and get the first result
                                var cpuNow = CloudWatchDataHelper.GetMetricsForEC2Instance(instanceHealthRaw.InstanceId, utcNow.AddSeconds(-120), utcNow, AnalyticsValueType.Maximum, CloudWatchDefaultMetricEC2.CPUUtilization, "Percent", periodSeconds: 60).OrderByDescending(x => x.TimeStamp).FirstOrDefault();


                                return new
                                            {
                                                RawId = instanceHealthRaw.InstanceId,
                                                Id =
                                                    instanceHealthRaw.InstanceId +
                                                    (instanceFriendlyName != null
                                                        ? String.Format(" ({0})", instanceFriendlyName)
                                                        : string.Empty),
                                                State = instanceHealthRaw.State,
                                                UpTime =
                                                    (DateTimeOffset.UtcNow -
                                                    DateTimeOffset.Parse(currentInstance.LaunchTime, null,
                                                                            DateTimeStyles.AssumeUniversal)).ToString(
                                                                                "c"),
                                                PublicDnsName = currentInstance.PublicDnsName,
                                                LaunchTime = currentInstance.LaunchTime,
                                                Reason = instanceHealthRaw.ReasonCode,
                                                CpuNow = Math.Round(cpuNow.Value, 2),
                                                Placement = currentInstance.Placement.AvailabilityZone
                                            };

                            }
               ).
               Union(badInstanceIds.Select(id =>
                   new {
                       RawId = id,
                       Id = id,
                       State = "[InstanceNotFound]",
                       UpTime = "",
                       PublicDnsName ="",
                       LaunchTime ="",
                       Reason ="",
                       CpuNow = 0d,
                       Placement = ""
                    })
               )
               ;

            return Json(result, JsonRequestBehavior.AllowGet);
        }
    }
}